{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# HDBSCAN* Clustering Tutorial\n",
    "\n",
    "This guide will show how to use Tribuo’s HDBSCAN* clustering package to find clusters and outliers using a toy dataset. We'll also look at how to visualize the results and make predictions for new data points. The details of this implementation are described in [this Applied Sciences journal article.](https://doi.org/10.3390/app12052405)\n",
    "\n",
    "## Setup\n",
    "\n",
    "We'll load in some jars and import a few packages. The xchart jar is needed for the plots, and can be downloaded from [Maven Central](https://search.maven.org/artifact/org.knowm.xchart/xchart/3.8.1/jar)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%jars ./tribuo-clustering-hdbscan-4.3.0-SNAPSHOT-jar-with-dependencies.jar\n",
    "%jars ./xchart-3.8.1.jar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import org.tribuo.*;\n",
    "import org.tribuo.clustering.*;\n",
    "import org.tribuo.clustering.hdbscan.*;\n",
    "import org.tribuo.data.columnar.*;\n",
    "import org.tribuo.data.columnar.processors.field.DoubleFieldProcessor;\n",
    "import org.tribuo.data.columnar.processors.response.EmptyResponseProcessor;\n",
    "import org.tribuo.data.csv.CSVDataSource;\n",
    "import org.tribuo.math.distance.DistanceType;\n",
    "import org.tribuo.math.neighbour.NeighboursQueryFactoryType;\n",
    "import org.knowm.xchart.*;\n",
    "import org.knowm.xchart.style.markers.*;\n",
    "import java.awt.Color;\n",
    "import java.nio.file.Paths;\n",
    "import java.util.*;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Declare some methods to help with plotting\n",
    "We'll declare a few methods that are useful for visualizing the results. If you're not really interested in how this is done, just skip down to the next heading."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method to get a new instance of a chart, configured the same way each time\n",
    "XYChart getNewXYChart(String title) {\n",
    "    XYChart chart = new XYChartBuilder().width(600).height(400).title(title).xAxisTitle(\"X\").yAxisTitle(\"Y\").build();\n",
    "    chart.getStyler().setDefaultSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Scatter);\n",
    "    chart.getStyler().setChartTitleVisible(false);\n",
    "    chart.getStyler().setLegendVisible(false);\n",
    "    chart.getStyler().setMarkerSize(8);\n",
    "    chart.getStyler().setPlotGridHorizontalLinesVisible(false);\n",
    "    chart.getStyler().setPlotGridVerticalLinesVisible(false);\n",
    "    return chart;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method to add a set of (x,y) points to a chart\n",
    "void addSeriesToChart(XYChart chart, List<Double> xList, List<Double> yList, String seriesName, Color color, Marker marker) {\n",
    "    XYSeries xYseries = chart.addSeries(seriesName,\n",
    "        xList.stream().mapToDouble(Double::doubleValue).toArray(),\n",
    "        yList.stream().mapToDouble(Double::doubleValue).toArray());\n",
    "    xYseries.setMarkerColor(color);\n",
    "    xYseries.setMarker(marker);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method to multiple sets of (x,y) points to a chart\n",
    "void addAllSeriesToChart(XYChart chart, Map<Integer, List<Double>> mapX, Map<Integer, List<Double>> mapY, Queue<Color> colors) {\n",
    "    for (Map.Entry<Integer, List<Double>> entry : mapX.entrySet()) {\n",
    "        if (entry.getKey() == 0) {    // The Outlier label\n",
    "            List<Double> xList = entry.getValue();\n",
    "            List<Double> yList = mapY.get(0);\n",
    "            addSeriesToChart(chart, xList, yList, \"Points\" + entry.getKey(), Color.darkGray, SeriesMarkers.CIRCLE);\n",
    "        } else {                      // Valid Cluster labels\n",
    "            List<Double> xList = entry.getValue();\n",
    "            List<Double> yList = mapY.get(entry.getKey());\n",
    "            addSeriesToChart(chart, xList, yList, \"Points\" + entry.getKey(), colors.poll(), SeriesMarkers.CIRCLE);\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method which extracts the (x,y) points from the dataset\n",
    "void setXandYListsFromDataset(List<Double> xList, List<Double> yList, Dataset<ClusterID> dataset) {\n",
    "    for (Example<ClusterID> ex : dataset) {\n",
    "        int i = 0;\n",
    "        for (Feature f : ex) {\n",
    "            if (i == 0)  xList.add(f.getValue());\n",
    "            if (i == 1)  yList.add(f.getValue());\n",
    "            i++;\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dataset\n",
    "The toy dataset we'll be using is a small set of 2-dimensional points. A dataset like this with so few samples and only 2 features isn't very realistic, but makes it easy to see how to use Tribuo's HDBSCAN* clustering package.\n",
    "\n",
    "First, we'll load the training data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "Map<String, FieldProcessor> regexMappingProcessors = new HashMap<>();\n",
    "regexMappingProcessors.put(\"Feature1\", new DoubleFieldProcessor(\"Feature1\"));\n",
    "regexMappingProcessors.put(\"Feature2\", new DoubleFieldProcessor(\"Feature2\"));\n",
    "RowProcessor<ClusterID> rowProcessor = new RowProcessor<>(new EmptyResponseProcessor<>(new ClusteringFactory()), regexMappingProcessors);\n",
    "\n",
    "var csvDataSource = new CSVDataSource<>(Paths.get(\"simple-2d-data/simple-2d-data-train.csv\"), rowProcessor, false);\n",
    "var dataset = new MutableDataset<>(csvDataSource);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we can look at a plot of the points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "BufferedImage@15ca1e7a: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "List<Double> xList = new ArrayList<>();\n",
    "List<Double> yList = new ArrayList<>();\n",
    "setXandYListsFromDataset(xList, yList, dataset);\n",
    "\n",
    "var chart = getNewXYChart(\"Dataset\");\n",
    "addSeriesToChart(chart, xList, yList, \"Points\", Color.blue, SeriesMarkers.CIRCLE);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Training\n",
    "We'll fit an HDBSCAN* model using a minimum cluster size of 5. This defines the minimum number of points required to form a cluster."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "var trainer = new HdbscanTrainer(5);\n",
    "var model = trainer.train(dataset);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Querying the model, we can see that 4 different cluster labels were identified. The cluster label 0 is the outlier label and indicates that some points in the dataset are marked as outliers or noise. That means the model found 3 distict clusters and some outlier points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 3, 4, 5]\n"
     ]
    }
   ],
   "source": [
    "var clusterLabels = model.getClusterLabels();\n",
    "var labelsSet = new HashSet<>(clusterLabels);\n",
    "System.out.println(labelsSet);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the cluster labels discovered by the model, we can organize the data to make it easy to visualize."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "Map<Integer, List<Double>> mapX = new HashMap<>();\n",
    "Map<Integer, List<Double>> mapY = new HashMap<>();\n",
    "int i = 0;\n",
    "for (Integer label : clusterLabels) {\n",
    "    List<Double> lx = mapX.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    lx.add(xList.get(i));\n",
    "    List<Double> ly = mapY.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    ly.add(yList.get(i));\n",
    "    i++;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's see that visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "BufferedImage@61e87a4f: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "// Since there are 3 distict clusters, we add 3 different colors to a queue to be used in the chart.\n",
    "Queue<Color> colors = new ArrayDeque<>();\n",
    "colors.add(Color.cyan); colors.add(Color.green); colors.add(Color.magenta);\n",
    "\n",
    "chart = getNewXYChart(\"Cluster Result\");\n",
    "addAllSeriesToChart(chart,  mapX,  mapY, colors);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outlier Scores\n",
    "An HDBSCAN* model calculates an outlier score for each point. These are values between 0 and 1, where points with higher outlier scores are more likely to be outliers. For example, we can query the model to see the outlier score for point (1.3, 4.31), which happens to be the point at index 9 in the dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8449704929196467\n"
     ]
    }
   ],
   "source": [
    "System.out.println(model.getOutlierScores().get(9));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Looking at the point (1.05, 1.0) which is near the center of the green cluster, we expect a lower outlier score. For the interested reader, this point is at index 4 in the dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.0\n"
     ]
    }
   ],
   "source": [
    "System.out.println(model.getOutlierScores().get(4));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Predictions\n",
    "Tribuo's HDBSCAN* clustering package conforms to the standard `Trainer` and `Model` interfaces used for the rest of Tribuo, so predictions can be made for new data points.\n",
    "\n",
    "Let's load a few new points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "CSVDataSource<ClusterID> csvPredictDataSource = new CSVDataSource<>(Paths.get(\"simple-2d-data/simple-2d-data-predict.csv\"), rowProcessor, false);\n",
    "Dataset<ClusterID> predictDataset = new MutableDataset<>(csvPredictDataSource);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The new points are: (4.25, 1.5) (3.9, 4.5) and (1.5, 3.0). We can add those to the existing plot of clusters using a red diamond marker to differentiate them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "BufferedImage@4b9f21fd: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "xList = new ArrayList<>();\n",
    "yList = new ArrayList<>();\n",
    "setXandYListsFromDataset(xList, yList, predictDataset);\n",
    "addSeriesToChart(chart, xList, yList, \"New Points\", Color.red, SeriesMarkers.DIAMOND);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's the make the prediction call."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "var predictions = model.predict(predictDataset);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the predicted cluster labels, we add the new points to the existing maps to prepare the updated data for visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "List<Integer> predictionLabels = new ArrayList<>();\n",
    "predictions.forEach(p -> predictionLabels.add(p.getOutput().getID()));\n",
    "\n",
    "i = 0;\n",
    "for (Integer label : predictionLabels) {\n",
    "    List<Double> lx = mapX.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    lx.add(xList.get(i));\n",
    "    List<Double> ly = mapY.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    ly.add(yList.get(i));\n",
    "    i++;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could have just printed out the cluster labels for the new points, but a visualization is better."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "BufferedImage@576e4bc6: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "// Again, we want to set the correct colors to be used in the chart.\n",
    "colors.add(Color.cyan); colors.add(Color.green); colors.add(Color.magenta);\n",
    "chart = getNewXYChart(\"Prediction Result\");\n",
    "addAllSeriesToChart(chart,  mapX,  mapY, colors);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The point (4.25, 1.5) is marked as an outlier. The point (1.5, 3.0) is shown to belong to the magenta colored cluster in the middle. Let's check the predicted outlier scores for these two points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The outlier score for (4.25, 1.5) is : 0.8492520788446892\n",
      "The outlier score for (1.5, 3.0) is : 0.02241334109160653\n"
     ]
    }
   ],
   "source": [
    "System.out.println(\"The outlier score for (4.25, 1.5) is : \" + predictions.get(0).getOutput().getScore());\n",
    "System.out.println(\"The outlier score for (1.5, 3.0) is : \" + predictions.get(2).getOutput().getScore());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimizing Training Performance\n",
    "Tribuo's HDBSCAN* package supports multi-threading for the core distance calculations step of the algorithm. For larger datasets, it is certainly worthwhile to train the model with multiple threads. \n",
    "\n",
    "Additionally the core distance calculations, which are just nearest neighbour queries, can be done using different techniques. The default technique uses a brute-force algorithm, which calculates the distances between all points and tracks the `k` smallest distances. The other technique uses a k-d tree algorithm, which significantly reduces the time to make nearest neighbour queries for most datasets. The k-d tree algorithm may use more memory when training a model compared with the brute-force algorithm. \n",
    "\n",
    "This is how to instantiate a trainer to use 4 threads, and the k-d tree algorithm. To use the brute-force algorithm instead, the fifth parameter would be `NeighboursQueryFactoryType.BRUTE_FORCE`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "var trainer = new HdbscanTrainer(5,    // The minimum cluster size\n",
    "                                 DistanceType.L2,  // The distance function \n",
    "                                 5,    // The number of neighbors to use to calculate the core-distance\n",
    "                                 4,    // The number of compute threads\n",
    "                                 NeighboursQueryFactoryType.KD_TREE  // The nearest neighbour query algorithm\n",
    "                                );"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "We looked at Tribuo's HDBSCAN\\* implementation and saw how to train a model, then make predictions with that model. HDBSCAN\\* stands for heirarchical density-based spatial clustering of applications with noise and yes, the original authors of the algorithm have used an asterisk in the name.\n",
    "\n",
    "We plan to further expand Tribuo's clustering functionality to incorporate other algorithms in the future. If you want to help, or have specific algorithmic requirements, file an issue on our [github page](https://github.com/oracle/tribuo)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java",
   "language": "java",
   "name": "java"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".jshell",
   "mimetype": "text/x-java-source",
   "name": "Java",
   "pygments_lexer": "java",
   "version": "11.0.12+8-LTS-237"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
